'''
Created on Aug 31, 2012

@author: koschke
'''
import numpy as np
import pygame

class TSPGame():
  '''
  game class
  holds background (map), coordinates, etc...
  holds handy functions
  '''
  
  def __init__(self, locationFile, knownMinimum=1e9, startingLocation=None):
    self._tolerance=20 #if user clicks, how far away can the city be and still gets selected?
    self._transparency=0.5
    self._scale='auto' #put 'auto' here for automatic scaling
    #determine scale later
    self._targetWidth=1200
    self._targetHeight=980
    #empirical value to convert pixels to meter - different for every map/resolution!
    self._pixToM=0.634782609 #real/(scaled pixels) # determine 'real' distances from the measure tool in google maps (labs)
    self._backgroundFile='map.png'
    self._linewidth=3
    self._solutionColor=(0,200,0)
    self._locationFile=locationFile #in abs values
    self._solutionFile=''
    self._knownMinimum=knownMinimum #previously calculated solution in m #hard:8749, easy: 4888 # still depends on resolution
    self._bg = pygame.image.load(self._backgroundFile)
    self._bgRect = self._bg.get_rect()
    (self._width, self._height) = self._bg.get_size()
    #determine scale - the window must fit into given dimensions, 
    #i.e., adjust to 'smaller' dimension
    if self._scale == 'auto':
      self._scale = self._targetWidth/float(self._width)
      __heightScale = self._targetHeight/float(self._height)
      if  __heightScale < self._scale: 
        self._scale=__heightScale
    
    self._width = int(round(self._width*self._scale))
    self._height = int(round(self._height*self._scale))
    self._size = (self._width, self._height)
    #print "creating background with size=",self._size
    #resize background
    self._bg = pygame.transform.smoothscale(self._bg,(self._width,self._height))
    #reload background rectangle
    self._bgRect = self._bg.get_rect()
    #create the screen
    self._window = pygame.display.set_mode(self._size)#todo:, pygame.RESIZABLE)
    self._window.blit(self._bg,self._bgRect)
    
    #put a white, transparent surface above that one
    self._transpwhite = pygame.Surface(self._size)
    self._transpwhite.fill((255,255,255))
    self._transpwhite.set_alpha(int(255*self._transparency))
    self._window.blit(self._transpwhite, self._bgRect)
    
    self.locations = ObjectList()
    self.sollocs = ObjectList()
    self.roads = ObjectList()
    self.trip = ObjectList()
    self.solution = ObjectList()
    self._toggleVisibleSolution = False

    self._coords = np.loadtxt(self._locationFile, delimiter=',')#, dtype='int')
    for entry in self._coords:
      self.locations.push(Location(entry,self._width, self._height))
      #print locations[-1].getCoords()

    self.startingLocation=startingLocation
    if startingLocation != None:
      self.trip.push(self.locations[startingLocation])
      self.locations[startingLocation].toggleStatus()
      


  def setSolution(self, _file):
    self._solutionFile = _file
    try:
      self._solCoords = np.loadtxt(self._solutionFile, delimiter=',')#, dtype='int')
      for entry in self._solCoords:
        self.sollocs.push(Location(entry, self._width, self._height))
    
      for i in range(self.sollocs.size() - 1):
        #test
        #self.solution.push(Road(self.sollocs.list()[i], self.sollocs.list()[i+1]))
        self.solution.push(Road(self.sollocs[i], self.sollocs[i+1]))
        self.solution.Last().setColor(self._solutionColor)
        self.solution.Last().setWidth(7)
    except:
      print "solution file %s not found!"%self._solutionFile
      exit(-1)
    
  def  toggleVisibleSolution(self):
    if self._solutionFile != '':
      self._toggleVisibleSolution = not self._toggleVisibleSolution
  
  def getLocationFile(self):
    return self._locationFile
  
  def getSolutionFile(self):
    return self._solutionFile
  
  def getKnownMinimum(self):
    return self._knownMinimum
    
  def distance(self, coordA, coordB): 
    __convert=self._pixToM/self._scale#take into account: 1)rescaling of the image, 2) pix to M conversion
    __tmp = 0.0
    for i in range(len(coordA)): #loop over dimensions
      __tmp += (coordA[i] - coordB[i])**2
    return np.sqrt(__tmp)*__convert

  def selectCity(self, coords):
    __result = None
    __minDist = 1e9 
    for loc in self.locations.list():
      __dist = self.distance(coords, loc.getCoords())
      if __dist > self._tolerance: continue
      #within radius
      if __dist < __minDist: 
        __result = loc
        __minDist = __dist
    return __result

  def calcTripLength(self, _trip):
    if len(_trip) < 2: return 0.0
    __tmp = 0.0
    for i in range(len(_trip)-1):
      __tmp += self.distance(_trip[i].getCoords(), _trip[i+1].getCoords())
    return __tmp

  def draw(self):
    __font = pygame.font.Font(None,36)
    #redraw background
    self._window.blit(self._bg,self._bgRect)
    self._window.blit(self._transpwhite, self._bgRect)
    #print known minimum
    __text = __font.render("Minimale Distanz: %dm"%self._knownMinimum, 1, self._solutionColor)
    #textpos = (width*0.6, height*0.15)
    __textpos = __text.get_rect(right=self._width, top=self._height*0.15)
    self._window.blit(__text,__textpos)
    #draw solution (roads) first
    if self._toggleVisibleSolution: 
      for loc in self.solution.list(): loc.draw(self._window)
    for location in self.locations.list():
      location.draw(self._window)
    for road in self.roads.list():
      road.draw(self._window)
    __triplength = int(round(self.calcTripLength(self.trip.list())))
    if self.trip.size() == self.locations.size() + 1:
      __text = __font.render("Gesamtdistanz: %dm"%(__triplength), 1, (0,0,0))
    else:
      __text = __font.render("Bisherige Distanz: %dm"%__triplength, 1, (0,0,0))
    #textpos = (width*0.6, height*0.1)#text.get_rect(centerx = window.get_width()*0.8,centery = window.get_height()*0.8)
    __textpos = __text.get_rect(right=self._width, top=self._height*0.1)
    self._window.blit(__text,__textpos)




class Location():
  '''
  class with information and methods for any Location
  '''
  def __init__(self, coords, width, height):
    '''
    constructor
    '''
    self.__coords = np.array([int(round(coords[0]*width)), int(round(coords[1]*height))])
    self.__status = False
    self.__radius = 10
    self.__color = (255, 0, 0)
    self.__color_last = (0, 0, 255)
    self.__visited = False
    
  #def visited(self):
  #  return self.__visited
  
  #def visit(self):
  #  self.__visited = True
    
  #def unvisit(self):
  #  self.__visited = False  
    
  def getCoords(self):
    return self.__coords
  
  def getStatus(self):
    return self.__status

  def toggleStatus(self):
    #print 'changing status from', self.__status,
    self.__status = not self.__status 
    #print 'to ',self.__status,' at loc %s'%self.getCoords()
    
  def setColor(self, color):
    self.__color = color
  
  def draw(self, surface):
    '''
    draws a circle at the coordinates of the Location
    '''
    #print "drawing a circle at %s" % self.__coords
    pygame.draw.circle(surface, self.__color, self.__coords, self.__radius, 0)
    if self.__status:
      pygame.draw.circle(surface, self.__color_last, self.__coords, int(self.__radius*1.5), 3)
    


class Road():
  '''
  class to represent a Road between two locations
  '''

  def __init__(self, locA, locB):
    '''
    constructor
    '''
    self.__locA = locA
    self.__locB = locB
    #self.__locations = np.array([locA, locB])
    self.__linecolor = (0, 0, 0)
    self.__linewidth = 3
    
  def setWidth(self, width):
    self.__linewidth = width
      
  def getWidth(self):
    return self.__linewidth
    
  def setColor(self, color):
    self.__linecolor = color
  
  def draw(self, surface):
    '''
    draws the Road
    '''
    #print "drawing a Road from %s to %s" % (self.__locA.getCoords(), self.__locB.getCoords())
    pygame.draw.line(surface, self.__linecolor, self.__locA.getCoords(), self.__locB.getCoords(), self.__linewidth)      
    #pygame.draw.aaline(surface, self.__linecolor, self.__locA.getCoords(), self.__locB.getCoords())#, self.__linewidth)      

#this is not a standalone module

class ObjectList():
  '''
  class that handles the kind of lists this game needs
  '''
  
  def __getitem__(self,i):
    return self._list[i]
  
  def __init__(self):
    self._size = 0
    self._list = []
  
  def Last(self):
    if self._list == []: return None
    return self[-1]

  def First(self):
    if self._list == []: return None
    return self[0]  
  
  def pop(self):
    if self._size != 0: self._size -= 1
    return self._list.pop()
  
  def push(self, item):
    self._size += 1
    self._list.append(item)
    
  def size(self):
    return self._size
  
  def has(self, item):
    return item in self._list
  
  def list(self):
    return self._list
